﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.ComponentModel;
using System.Collections.ObjectModel;
using DnDCharacterBuilder.Classes;
using DnDCharacterBuilder.Data;

namespace DnDCharacterBuilder
{
    [Serializable]
    public class CharacterData : INotifyPropertyChanged
    {
        // Constructor
        public CharacterData()
        {
            Str = 10;
            Con = 10;
            Dex = 10;
            Intel = 10;
            Wis = 10;
            Chara = 10;

            Gender = "Male";
            Race = "DragonBorn";
            Class = "Cleric";
            Level = 1;

            if (Armor.Count == 0)
                Armor.AddArmorRow("Default", 0, 0, 0, 0, 0, false, 0);
            if (Weapons.Count == 0)
                Weapons.AddWeaponsRow("Default", 0, "1d1", "10/20", 0, 0, "None", "-", false, 0);
            if (Equip.Count == 0)
                Equip.AddEquipmentRow("Default", 0, 0, "Notes", false, 0);

        }

        // Property change handler
        [field: NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        #region Character Details

        private string name;
        public string Name
        {
            get { return name; }
            set
            {
                name = value;
                Update();
            }
        }

        public ObservableCollection<string> GenderCollection { get { return new ObservableCollection<string>() { "Male", "Female" }; } }
        private string gender;
        public string Gender
        {
            get { return gender; }
            set
            {
                gender = value;
                Update();
            }
        }

        public ObservableCollection<string> RaceCollection { get { return new ObservableCollection<string>() { "DragonBorn", "Dwarf", "Eladrin", "Elf",
                                                                                                               "Half-Elf", "Halfling", "Human", "Tiefling" }; } }
        CharRace charRace;
        private string race;
        public string Race
        {
            get { return race; }
            set
            {
                race = value;
                if (race == "DragonBorn")
                    charRace = new DragonBornRace();
                else if (race == "Dwarf")
                    charRace = new DwarfRace();
                else if (race == "Eladrin")
                    charRace = new EladrinRace();
                else if (race == "Elf")
                    charRace = new ElfRace();
                else if (race == "Half-Elf")
                    charRace = new HalfElfRace();
                else if (race == "Halfling")
                    charRace = new HalflingRace();
                else if (race == "Human")
                    charRace = new HumanRace();
                else if (race == "Tiefling")
                    charRace = new TieflingRace();
                Update();
            }
        }
        public string RaceRef { get { return "Images\\RaceRef\\" + Race + ".png"; } } 

        public ObservableCollection<string> ClassCollection { get { return new ObservableCollection<string>() { "Cleric", "Fighter", "Paladin", "Ranger",
                                                                                                                "Rogue", "Warlock", "Warlord", "Wizard"}; } }
        CharClass charClass;
        private string _class;
        public string Class
        {
            get { return _class; }
            set
            {
                _class = value;
                if (_class == "Cleric")
                    charClass = new ClericClass();
                else if (_class == "Fighter")
                    charClass = new FighterClass();
                else if (_class == "Paladin")
                    charClass = new PaladinClass();
                else if (_class == "Ranger")
                    charClass = new RangerClass();
                else if (_class == "Rogue")
                    charClass = new RogueClass();
                else if (_class == "Warlock")
                    charClass = new WarlockClass();
                else if (_class == "Warlord")
                    charClass = new WarlordClass();
                else if (_class == "Wizard")
                    charClass = new WizardClass();
                Update();
            }
        }
        public string ClassRef { get { return "Images\\ClassRef\\" + Class + ".png"; } } 

        private int level;
        public int Level
	    {
		    get { return level;}
            set
            {
                level = value;
                Update();
            }
	    }
        public int LevelMod { get { return (int)Math.Floor(Level / 2.0); } } 

        private int currHealth;
        public int CurrHealth
        {
            get { return currHealth; }
            set { currHealth = value; }
        }

        public int MaxHealth { get { return charClass.HealthInit + Con + ((Level - 1) * charClass.HealthPerLevel); } }

        public int SurgesPerDay { get { return charClass.SurgePerDay + ConMod; } }
        public int SurgeValue { get { return (int)Math.Floor(MaxHealth / 4.0); } }

        private string classBuild;
        public string ClassBuild
        {
            get { return classBuild; }
            set { classBuild = value; Update(); }
        }

        private string alignment;
        public string Alignment
        {
            get { return alignment; }
            set { alignment = value; Update(); }
        }

        private string diety;
        public string Diety
        {
            get { return diety; }
            set { diety = value; Update(); }
        }

        private int experience;
        public int Experience
        {
            get { return experience; }
            set { experience = value; Update(); }
        }

        public int ExpToLevel
        {
            get 
            {
                switch (Level)
                {
                    case 1:
                        return 1000;
                    case 2:
                        return 2250;
                    case 3:
                        return 3750;
                    case 4:
                        return 5500;
                    case 5:
                        return 7500;
                    case 6:
                        return 10000;
                    case 7:
                        return 13000;
                    case 8:
                        return 16500;
                    case 9:
                        return 20500;
                    case 10:
                        return 26000;
                    case 11:
                        return 32000;
                    case 12:
                        return 39000;
                    case 13:
                        return 47000;
                    case 14:
                        return 57000;
                    case 15:
                        return 69000;
                    case 16:
                        return 83000;
                    case 17:
                        return 99000;
                    case 18:
                        return 119000;
                    case 19:
                        return 143000;
                    case 20:
                        return 175000;
                    case 21:
                        return 210000;
                    case 22:
                        return 255000;
                    case 23:
                        return 310000;
                    case 24:
                        return 375000;
                    case 25:
                        return 450000;
                    case 26:
                        return 550000;
                    case 27:
                        return 675000;
                    case 28:
                        return 825000;
                    case 29:
                        return 1000000;                        

                    default:
                        return 0;
                }


            }
        }

        private Money charMoney = new Money();
        public Money CharMoney
        {
            get { return charMoney; }
            set { charMoney = value; Update(); }
        }

        private Money diffMoney = new Money();
        public Money DiffMoney
        {
            get { return diffMoney; }
            set { diffMoney = value; Update(); }
        }

        private string otherNotes = "";
        public string OtherNotes
        {
            get { return otherNotes; }
            set { otherNotes = value; Update(); }
        }


        #endregion

        #region Base Stats
        private int str;
        public int StrMod
        {
            get { return (int)Math.Floor((str + StrAdj + charRace.StrBonus) / 2.0) - 5; }
        }
        public int Str
        {
            get { return str; }
            set
            {
                str = value;
                Update();
            }
        }
        private int strAdj;
        public int StrAdj
        {
            get { return strAdj; }
            set { strAdj = value; Update(); }
        }
                
        private int con;
        public int ConMod
        {
            get { return (int)Math.Floor((con + ConAdj + charRace.ConBonus) / 2.0) - 5; }
        }
        public int Con
        {
            get { return con; }
            set
            {
                con = value;
                Update();
            }
        }
        private int conAdj;
        public int ConAdj
        {
            get { return conAdj; }
            set { conAdj = value; Update(); }
        }
        
        private int dex;
        public int DexMod
        {
            get { return (int)Math.Floor((dex + DexAdj + charRace.DexBonus) / 2.0) - 5; }
        }
        public int Dex
        {
            get { return dex; }
            set
            {
                dex = value;
                Update();
            }
        }
        private int dexAdj;
        public int DexAdj
        {
            get { return dexAdj; }
            set { dexAdj = value; Update(); }
        }

        private int intel;
        public int IntelMod
        {
            get { return (int)Math.Floor((intel + IntelAdj + charRace.IntelBonus) / 2.0) - 5; }
        }
        public int Intel
        {
            get { return intel; }
            set
            {
                intel = value;
                Update();
            }
        }
        private int intelAdj;
        public int IntelAdj
        {
            get { return intelAdj; }
            set { intelAdj = value; Update(); }
        }

        private int wis;
        public int WisMod
        {
            get { return (int)Math.Floor((wis + WisAdj + charRace.WisBonus) / 2.0) - 5; }
        }
        public int Wis
        {
            get { return wis; }
            set
            {
                wis = value;
                Update();
            }
        }
        private int wisAdj;
        public int WisAdj
        {
            get { return wisAdj; }
            set { wisAdj = value; Update(); }
        }
                
        private int chara;
        public int CharaMod
        {
            get { return (int)Math.Floor((chara + CharaAdj + charRace.CharaBonus) / 2.0) - 5; }
        }
        public int Chara
        {
            get { return chara; }
            set
            {
                chara = value;
                Update();
            }
        }
        private int charaAdj;
        public int CharaAdj
        {
            get { return charaAdj; }
            set { charaAdj = value; Update(); }
        }


        #endregion

        #region Defenses

        public int AC
        {
            get     // #Add armor modifier
            {
                return 10 + (int)Math.Floor(level / 2.0) + GreaterOf(DexMod, IntelMod) + 0 + ACMod;
            }  
        }

        private int acMod;
        public int ACMod
        {
            get { return acMod; }
            set { acMod = value; Update(); }
        }


        public int Fort
        {
            get     
            {
                return 10 + (int)Math.Floor(level / 2.0) + GreaterOf(ConMod, StrMod) + charClass.FortBonus + charRace.FortBonus + FortMod;
            }
        }

        private int fortMod;
        public int FortMod
        {
            get { return fortMod; }
            set { fortMod = value; Update(); }
        }


        public int Reflex
        {
            get     
            {
                return 10 + (int)Math.Floor(level / 2.0) + GreaterOf(IntelMod, DexMod) + charClass.ReflexBonus + charRace.ReflexBonus + ReflexMod;
            }
        }

        private int reflexMod;
        public int ReflexMod
        {
            get { return reflexMod; }
            set { reflexMod = value; Update(); }
        }


        public int Will
        {
            get    
            {
                return 10 + (int)Math.Floor(level / 2.0) + GreaterOf(CharaMod, WisMod) + charClass.WillBonus + charRace.WillBonus + WillMod;
            }
        }

        private int willMod;
        public int WillMod
        {
            get { return willMod; }
            set { willMod = value; Update(); }
        }


        public string DefenseNotes { get; set; } 



        #endregion

        #region Mechanics
        // #Does not include feat
        public int Initiative { get { return LevelMod + DexMod; } }
        public int Speed { get { return charRace.Speed; } }
        public int Shift { get { return 0; } } // #Do not understand

        // Nice, but not useful here.
        //public ObservableCollection<string> VisionOptions { get { return new ObservableCollection<string>() { "Normal", "Low-Light" }; } }
        public string Vision { get { return charRace.Vision; } }

        // Nice, but not useful here.
        //public ObservableCollection<string> SizeOptions { get { return new ObservableCollection<string>() { "Tiny", "Small", "Medium", "Large", "Huge", "Gargantuan" }; } } 
        public string Size { get { return charRace.Size; } }

        public ObservableCollection<string> LanguageOptions { get { return new ObservableCollection<string>() { "Common", "Draconic", "Dwarvish", "Elvish" }; } }
        private ObservableCollection<string> selectedLanguages;
        public ObservableCollection<string> SelectedLanguages
        {
            get { return selectedLanguages; }
            set { selectedLanguages = value; }
        }

        //public List<string> Languages { get; } 
        #endregion

        #region Skills
        /*
         *  Acrobatics
            Arcana
            Athletics
            Bluff
            Diplomacy
            Dungeoneering
            Endurance
            Heal
            History
            Insight
            Intimidate
            Nature
            Perception
            Religion
            Stealth
            Streetwise
            Thievery
         */

        // # Add additional modifiers

        public int AcroSkill
        { 
            get 
            {
                return (int)Math.Floor(Level / 2.0) + DexMod + isTrained(AcroIsTrained) + charRace.AcrobaticsBonus;
            } 
        }
        private bool acroIsTrained;
        public bool AcroIsTrained
        {
            get { return acroIsTrained; }
            set
            {
                acroIsTrained = value;
                Update();
            }
        }

        public int ArcaSkill
        {
            get
            {
                return (int)Math.Floor(Level / 2.0) + IntelMod + isTrained(ArcaIsTrained) + charRace.ArcanaBonus;
            }
        }
        private bool arcaIsTrained;
        public bool ArcaIsTrained
        {
            get { return arcaIsTrained; }
            set
            {
                arcaIsTrained = value;
                Update();
            }
        }

        public int AthSkill
        {
            get
            {
                return (int)Math.Floor(Level / 2.0) + StrMod + isTrained(AthIsTrained) + charRace.AthleticsBonus;
            }
        }
        private bool athIsTrained;
        public bool AthIsTrained
        {
            get { return athIsTrained; }
            set
            {
                athIsTrained = value;
                Update();
            }
        }

        public int BluffSkill
        {
            get
            {
                return (int)Math.Floor(Level / 2.0) + CharaMod + isTrained(BluffIsTrained) + charRace.BluffBonus;
            }
        }
        private bool bluffIsTrained ;
        public bool BluffIsTrained 
        {
            get { return bluffIsTrained ; }
            set
            {
                bluffIsTrained = value; 
                Update();
            }
        }

        public int DipSkill
        {
            get
            {
                return (int)Math.Floor(Level / 2.0) + CharaMod + isTrained(DipIsTrained) + charRace.DiplomacyBonus;                
            }
        }
        private bool dipIsTrained ;
        public bool DipIsTrained 
        {
            get { return dipIsTrained ; }
            set
            {
                dipIsTrained = value;
                Update();
            }
        }

        public int DungSkill
        {
            get
            {
                return (int)Math.Floor(Level / 2.0) + WisMod + isTrained(DungIsTrained) + charRace.DungeoneeringBonus;
            }
        }
        private bool dungIsTrained;
        public bool DungIsTrained
        {
            get { return dungIsTrained; }
            set
            {
                dungIsTrained = value;
                Update();
            }
        }

        public int EndSkill
        {
            get
            {
                return (int)Math.Floor(Level / 2.0) + ConMod + isTrained(EndIsTrained) + charRace.EnduranceBonus;
            }
        }
        private bool endIsTrained ;
        public bool EndIsTrained 
        {
            get { return endIsTrained ; }
            set
            {
                endIsTrained = value;
                Update();
            }
        }

        public int HealSkill
        {
            get
            {
                return (int)Math.Floor(Level / 2.0) + WisMod + isTrained(HealIsTrained) + charRace.HealBonus;
            }
        }
        private bool healIsTrained ;
        public bool HealIsTrained 
        {
            get { return healIsTrained ; }
            set
            {
                healIsTrained = value;
                Update();
            }
        }

        public int HistSkill
        {
            get
            {
                return (int)Math.Floor(Level / 2.0) + IntelMod + isTrained(HistIsTrained) + charRace.HistoryBonus;
            }
        }
        private bool histIsTrained ;
        public bool HistIsTrained 
        {
            get { return histIsTrained ; }
            set
            {
                histIsTrained = value;
                Update();
            }
        }


        public int InsSkill
        {
            get
            {
                return (int)Math.Floor(Level / 2.0) + WisMod + isTrained(InsIsTrained) + charRace.InsightBonus;
            }
        }
        private bool insIsTrained ;
        public bool InsIsTrained 
        {
            get { return insIsTrained ; }
            set
            {
                insIsTrained = value;
                Update();
            }
        }


        public int IntSkill
        {
            get
            {
                return (int)Math.Floor(Level / 2.0) + CharaMod + isTrained(IntIsTrained) + charRace.IntimidateBonus;
            }
        }
        private bool intIsTrained ;
        public bool IntIsTrained 
        {
            get { return intIsTrained ; }
            set
            {
                intIsTrained = value;
                Update();
            }
        }


        public int NatSkill
        {
            get
            {
                return (int)Math.Floor(Level / 2.0) + WisMod + isTrained(NatIsTrained) + charRace.NatureBonus;
            }
        }
        private bool natIsTrained ;
        public bool NatIsTrained 
        {
            get { return natIsTrained ; }
            set
            {
                natIsTrained = value;
                Update();
            }
        }


        public int PercSkill
        {
            get
            {
                return (int)Math.Floor(Level / 2.0) + WisMod + isTrained(PercIsTrained) + charRace.PerceptionBonus;
            }
        }
        private bool percIsTrained ;
        public bool PercIsTrained 
        {
            get { return percIsTrained ; }
            set
            {
                percIsTrained = value;
                Update();
            }
        }

        public int RelSkill
        {
            get
            {
                return (int)Math.Floor(Level / 2.0) + IntelMod + isTrained(RelIsTrained) + charRace.ReligionBonus;
            }
        }
        private bool relIsTrained ;
        public bool RelIsTrained 
        {
            get { return relIsTrained ; }
            set
            {
                relIsTrained = value;
                Update();
            }
        }

        public int StSkill
        {
            get
            {
                return (int)Math.Floor(Level / 2.0) + DexMod + isTrained(StIsTrained) + charRace.StealthBonus;
            }
        }
        private bool stIsTrained ;
        public bool StIsTrained 
        {
            get { return stIsTrained ; }
            set
            {
                stIsTrained = value;
                Update();
            }
        }


        public int StreetSkill
        {
            get
            {
                return (int)Math.Floor(Level / 2.0) + CharaMod + isTrained(StreetIsTrained) + charRace.StreetwiseBonus;
            }
        }
        private bool streetIsTrained;
        public bool StreetIsTrained 
        {
            get { return streetIsTrained; }
            set
            {
                streetIsTrained = value;
                Update();
            }
        }


        public int ThiefSkill
        {
            get
            {
                return (int)Math.Floor(Level / 2.0) + DexMod + isTrained(ThiefIsTrained) + charRace.ThieveryBonus;
            }
        }
        private bool thiefIsTrained;
        public bool ThiefIsTrained
        {
            get { return thiefIsTrained; }
            set
            {
                thiefIsTrained = value;
                Update();
            }
        }


        #endregion

        #region Abilities

        private ObservableCollection<EListItem> atWillCollection = new ObservableCollection<EListItem>() { };
        public ObservableCollection<EListItem> AtWillCollection
        {
            get { return atWillCollection; }
            set { atWillCollection = value; } 
        }

        private ObservableCollection<EListItem> encounterCollection = new ObservableCollection<EListItem>() { };
        public ObservableCollection<EListItem> EncounterCollection
        {
            get { return encounterCollection; }
            set { encounterCollection = value; }
        }

        private ObservableCollection<EListItem> dailyCollection = new ObservableCollection<EListItem>() { };
        public ObservableCollection<EListItem> DailyCollection 
        {
            get { return dailyCollection; }
            set { dailyCollection = value; }
        } 

        #endregion

        #region Equipment
        private EquipmentData.ArmorDataTable armor = new EquipmentData.ArmorDataTable();
        public EquipmentData.ArmorDataTable Armor
        {
            get { return armor; }
            set { armor = value; Update(); }
        }

        //public int ArmorAC 
        //{ 
        //    get 
        //    {                
        //        return 0; 
        //    } 
        //}

        private EquipmentData.WeaponsDataTable weapons = new EquipmentData.WeaponsDataTable();
        public EquipmentData.WeaponsDataTable Weapons
        {
            get { return weapons; }
            set { weapons = value; Update(); }
        }

        private EquipmentData.EquipmentDataTable equip = new EquipmentData.EquipmentDataTable();
        public EquipmentData.EquipmentDataTable Equip
        {
            get { return equip; }
            set { equip = value; Update(); }
        }

        #endregion

        #region Methods
        private int GreaterOf(int i, int j)
        {
            if (i > j)
                return i;
            else
                return j;
        }

        private double GreaterOf(double i, double j)
        {
            if (i > j)
                return i;
            else
                return j;
        }

        private int isTrained(bool check)
        {
            if (check)
                return 5;
            else
                return 0;
        }

        public void Update()
        {
            // Notify all changes
            
            NotifyPropertyChanged("Name");
            NotifyPropertyChanged("Gender");
            NotifyPropertyChanged("Race");
            NotifyPropertyChanged("RaceRef");
            NotifyPropertyChanged("Class");
            NotifyPropertyChanged("Level");
            NotifyPropertyChanged("MaxHealth");
            NotifyPropertyChanged("CurrHealth");                      
            NotifyPropertyChanged("SurgesPerDay");
            NotifyPropertyChanged("SurgeValue");
            NotifyPropertyChanged("OtherNotes");

            NotifyPropertyChanged("StrMod");
            NotifyPropertyChanged("ConMod");
            NotifyPropertyChanged("DexMod");
            NotifyPropertyChanged("IntelMod");
            NotifyPropertyChanged("WisMod");
            NotifyPropertyChanged("CharaMod");

            NotifyPropertyChanged("AC");
            NotifyPropertyChanged("Fort");
            NotifyPropertyChanged("Reflex");
            NotifyPropertyChanged("Will");
            NotifyPropertyChanged("DefenseNotes");

            NotifyPropertyChanged("Initiative");
            NotifyPropertyChanged("Speed");
            NotifyPropertyChanged("Size");
            NotifyPropertyChanged("Shift");
            NotifyPropertyChanged("Vision");
            NotifyPropertyChanged("Languages");
            NotifyPropertyChanged("SelectedLanguages");

            NotifyPropertyChanged("ClassBuild");
            NotifyPropertyChanged("Alignment");
            NotifyPropertyChanged("Diety");
            NotifyPropertyChanged("Experience");
            NotifyPropertyChanged("ExpToLevel");
            NotifyPropertyChanged("CharMoney");
            NotifyPropertyChanged("DiffMoney");

            NotifyPropertyChanged("AcroSkill");
            NotifyPropertyChanged("ArcaSkill");
            NotifyPropertyChanged("AthSkill");
            NotifyPropertyChanged("BluffSkill");
            NotifyPropertyChanged("DipSkill");
            NotifyPropertyChanged("DungSkill");
            NotifyPropertyChanged("EndSkill");
            NotifyPropertyChanged("HealSkill");
            NotifyPropertyChanged("HistSkill");
            NotifyPropertyChanged("InsSkill");
            NotifyPropertyChanged("IntSkill");
            NotifyPropertyChanged("NatSkill");
            NotifyPropertyChanged("PercSkill");
            NotifyPropertyChanged("RelSkill");
            NotifyPropertyChanged("StSkill");
            NotifyPropertyChanged("StreetSkill");
            NotifyPropertyChanged("ThiefSkill");

            NotifyPropertyChanged("Armor");
            NotifyPropertyChanged("Weapons");
            NotifyPropertyChanged("Equip");

        }

        #endregion

    }

    [Serializable]
    public class Money
    {
        // conversion between types
        int multiplier = 100;

        private int copper;
	    public int Copper
	    {
		    get { return copper;}
		    set 
            {
                if (value >= multiplier)
                {
                    copper = value - multiplier;
                    Silver++;
                }
                else if (value < 0)
                {
                    copper = value + multiplier;
                    Silver--;
                }
                else
                    copper = value;
            }
	    }

        private int silver;

	    public int Silver
	    {
		    get { return silver;}
		    set 
            {
                if (value >= multiplier)
                {
                    silver = value - multiplier;
                    Gold++;
                }
                else if (value < 0)
                {
                    silver = value + multiplier;
                    Gold--;
                }
                else
                    silver = value;
            }
	    }

            private int gold;

	    public int Gold
	    {
		    get { return gold;}
		    set { gold = value;}
	    }


        public Money(int g=0, int s=0, int c=0)
        {
            Gold = g;
            Silver = s;
            Copper = c; 
        }

        public static Money operator+(Money a, Money b)
        {
            if (a != null && b != null)
                return new Money(g: a.Gold + b.Gold, s: a.Silver + b.Silver, c: a.Copper + b.Copper);
            else
                return new Money();
        }

        public override string ToString()
        {
            return Gold.ToString() + "g " +
                Silver.ToString() + "s " +
                Copper.ToString() + "c";
        }

        public static Money operator-(Money a, Money b)
        {
            return new Money(g: a.Gold - b.Gold, s: a.Silver - b.Silver, c: a.Copper - b.Copper);
        }
    }

}
